[CODE BLUE 2018] イーサリアム・スマートコントラクトにある擬似乱数ジェネレータの破壊 ジョンヒョク・ソン [レポート] #codeblue_jp
こんにちは、臼田です。
『世界トップクラスのセキュリティ専門家による日本発の情報セキュリティ国際会議』でありますCODE BLUE 2018に参加していますのでレポートします。
このブログは下記セッションについてのレポートです。
イーサリアム・スマートコントラクトにある擬似乱数ジェネレータの破壊 ジョンヒョク・ソン
イーサリアムのスマートコントラクトにおける乱数生成は非常に難しい。それは複数のノードにより何度実行されても同じ結果を返す必要があるからである。 しかし、実際にはゲーム、宝くじ、ギャンブルなどの多くのスマートコントラクトにおいては独自の乱数生成器(PRNG)が実装されている。それらは多くが脆弱であるため、攻撃者はマイナーでなくとも簡単に攻撃することができる。 多くの研究者がスマートコントラクトの検証ツールを提案しているが、それらのツールが実際に脆弱なPRNGを検出できたことはなかった。
この講演では脆弱なPRNGの種類を分類し、その実例について示す。また、さらに詳細に分析を行い、実際にそれらを悪用(エクスプロイト)する方法について説明を行う。 今回紹介する脆弱なPRNGには主に以下の4つの種類がある。
1. 古いブロックのブロックハッシュとプライベート変数に基づくPRNG - コントラクトの例: イーサリアム めんこゲーム - ゴール: ゲームに勝利し、他のプレーヤーのすべてのめんこを手に入れる 2. 現ブロックのブロックハッシュとプライベート変数に基づくPRNG - ゴール: レジェンドチャンピオンの獲得 3. 最後のブロックのブロックハッシュとプライベート変数に基づくPRNG - コントラクト例: イーサリアム RPGゲーム - ゴール: レジェンドチャンピオンとアイテムの獲得 4. ブロック変数とプライベート変数に基づくPRNG - コントラクト例: イーサリアム ギャンブルゲーム - ゴール: 勝利と賞金の獲得
上記のPRNGをどのように悪用するかを説明するにあたって、まずはイーサリアムとSolidityについてのいくつか説明を行う。具体的にはSolidityのblockhash()関数の特性、スマートコントラクトのプライベート変数、2つのコントラクト間の取引について説明を行い、最後にスマートコントラクトにおいて安全に乱数を生成する方法について紹介する予定だ。
レポート
- 話のゴール
- 脆弱なPRNG(擬似乱数ジェネレータ)についての理解
- どのように攻撃するか
- サマリ
- 脆弱なゲームとギャンブルのコントラクトをいくつか見つけている
- スマートコントラクトでの乱数生成
- ゲーム系でもよく使われる
- RPGではキャラ生成やバトルでも利用される
- しかしスマートコントラクトで乱数生成を生成することは難しい
- 複数のノードで動いているため
- 殆どの場合にはtimestampとシードを利用している
- 大丈夫なのか
- private変数でもblockchainに記述されている
- Block変数はマイナーによって操作することもできる
- 場合によってはマイナーが値を操作することもできる
- 脆弱なスマートコントラクト
- 4種類のPRNGs
- 例1
- blockhash(Block)
- 256以上古いBlockを指定した場合には0となっていまう
- keccak256(_reveal, blockhash(block))
- blockhashが0にできるので_revealを上手くやればいい
- 例2
- keccak256(keccak256(0, seed),time)
- blockhash(block)は同じ
- seedをどうするか
- seedはprivate
- プライベート変数の読み方
- web3.eth.getStorageAt(address, position)
- プライベート変数は別ユーザからの変更は防止しているが、読める
- コントラクトアドレスとpositionを知っていればいい
- 関数の定義からseedを読むことが可能
- timeは9950より大きい値を指定したい(ゲーム内で強いヒーローを作りたいため)
- transactionを自分の望んだタイミングで行うことは難しい
- そのため正確な時間はわからない
- internal transactionを活用する
- Internal Transaction
- 他のコントラクトから別のコントラクトの関数を実行する
- コントラクトAとBが同じBlock変数を利用する
- 希望の値が作れたらInternal Transactionで対象のコントラクトを呼ぶ
- timeの値をチェックするだけのコントラクトを作成してInternal Transactionを利用する
- 例3
- blockhash(block.number-1)
- Last blockを利用している
- 例としてMyCryptoChamp
- randMod()関数を使っている
- この関数で作るアイテムのレアリティを決めている
- keccac256(randNonce, blockhash(block.number-1))
- randNonceはプライベート変数だが読める
- blocknumberはトランザクション前にはわからない
- Internal Transactionsで攻撃
- 乱数を生成して980以下ならrevert()する
- 例4
- keccak256(block.timestamp, block.difficulty, block.coinbase)
- 1000 Guess
- ギャンブルゲーム
- 0.01etherをベットできる
- 10ユーザがベットしたら勝敗が決る
- curhash = sha256(block.timestamp, block.difficulty, block.coinbase, curhash)
- curhashはプライベート変数で読める
- block変数はわからないのでInternal Transaction
- 同じ乱数生成を行って、自分が勝者になる場合のみ投げる
- 失敗している分余分に消費しているが、見返りに比べると安い
- 対策
- blockchainはこれから発展するジャンルなので、間違っていたらすいません
- 使わないこと
- blockhash(blocknumber)
- 0になるので使わない
- block.number
- block.number-1
- 過去のブロック番号も脆弱
- block.timestampなどブロック変数も脆弱
- private変数も読めるから駄目
- どうすれば安全に生成できるか
- commitment scheme with the future block
- commit phaseとreveal phaseが必要になる
- イーサリアムのブロックチェーン外のdataを利用する
- Oraclizeなど
- 外部サーバにリクエストして、randum.orgで生成して返す
- commitment scheme with the future block
感想
ブロックチェーン上での疑似乱数の利用の難しさを感じました。今のところは外部ソースを利用するほうが良さそうですね。